<?php
class Core_Model_Layout {

	protected $_handles = array();

	protected $_loadedHandles = array();

	protected $_layoutXml;

	protected $_blocks = array();

	protected $_rootBlocks = array();

	public function __construct() {
		// init layout xml object
		$xmlTag = '<'.'?xml version="1.0"?'.'>';
		$this->_layoutXml = simplexml_load_string($xmlTag.'<layout></layout>');
	}

	public function addXml($s) {
		// append node to layout xml object
		$xml = simplexml_load_string($s);
		foreach($xml->children() as $c)
			Mag::helper('Core')->appendXml($this->_layoutXml, $c);
	}
	
	public function addHandle($handle) {
		$this->_handles[] = $handle;
	}
	
	public function load() {
		foreach($this->_handles as $handle)
			$this->_applyHandle($handle);
	}
	
	public function loadXml($s) {
		$xmlTag = '<'.'?xml version="1.0"?'.'>';
		$update = simplexml_load_string($xmlTag.'<update>'.$s.'</update>');
		$this->_parseNode($update);
	}
	
	public function createBlock($option) {
		$name = $option['name'];
		$class = $option['type'];
		$block = Mag::object($class, $option);
		$this->_blocks[$name] = $block;
		return $block;
	}

	public function getBlock($name) {
		return isset($this->_blocks[$name]) ? $this->_blocks[$name] : null;
	}

	public function removeBlock($name) {
		unset($this->_blocks[$name]);
		return $this;
	}
	
	public function getOutput() {
		$output = '';
		foreach($this->_rootBlocks as $block) {
			$output .= $block->getOutput();
		}

		return $output;
	}
	
	protected function _applyHandle($handle) {
		if (in_array($handle, $this->_loadedHandles)) return;
		
		// find nodes for this handle
		foreach ($this->_layoutXml->children() as $h) {	
			if ($h->getName() != $handle) continue;
			
			// load parent handle
			if ($parent = Mag::helper('Core')->getNodeAttribute($h, 'parent')) {
				$this->_applyHandle($parent);
			}

			// load handle
			$this->_parseNode($h);
			$this->_loadedHandles[] = $handle;
		}
	}
	
	protected function _parseNode($parent) {
		foreach ($parent->children() as $node) {
			switch ($node->getName()) {
				case 'block':
					$this->_generateBlock($node, $parent);
					$this->_parseNode($node);
					break;

				case 'reference':
					$this->_parseNode($node);
					break;

				case 'action':
					$this->_generateAction($node, $parent);
					break;
					
				case 'remove':
					$this->_generateRemove($node);
					break;
			}
		}
	}
	
	protected function _generateBlock($node, $parent) {
		// create block & add to layout
		$option = array();
		foreach($node->attributes() as $k => $v)
			$option[(string)$k] = (string)$v;
		$block = $this->createBlock($option);
		
		// add child block to parent block
		if ($pName = Mag::helper('Core')->getNodeAttribute($parent, 'name')) {
			$parentBlock = $this->getBlock($pName);
			$parentBlock->addChild($block->getName());
		} else {
			// use when output layout
			$this->_rootBlocks[] = $block;
		}
	}
	
	protected function _generateAction($node, $parent) {
		// get block method name
		$helper = Mag::helper('Core');
		$method = $helper->getNodeAttribute($node, 'method');
		
		// prepare method arguments
		$args = array();
		foreach($node->children() as $c)
			$args[] = (string)$c;

		// get block & call block method
		$blockName = $helper->getNodeAttribute($parent, 'name');
		$block = $this->getBlock($blockName);
		if ($block)	call_user_func_array(array($block, $method), $args);
	}
	
	protected function _generateRemove($node) {
		// remove block
		$blockName = Mag::helper('Core')->getNodeAttribute($node, 'name');
		$block = $this->getBlock($blockName);
		$this->removeBlock($blockName);
	}
}